Pro ASP.NET Core MVC2(第7版)翻译

第6章:使用 Visual Studio

作者:Adam Freeman 翻译:陈广 日期:2018-8-26


本章,我将描述 Visual Studio 为开发 ASP.NET Core MVC 项目所提供的关键功能。表6-1为本章摘要。

表 6-1:章节摘要

问题 解决方案 清单
为项目添加开发包 .NET 包使用 NuGet 工具,客户端包使用 Bower 6-8
查看视图或类更改的效果 使用迭代开发模型 9-11
在浏览器中显示详细消息 使用开发者异常页 12
应用程序执行的详细信息获取和控制 使用调试器 13
使用 Visual Studio 重新加载一个或多个浏览器 使用浏览器链接 14-15
减少 HTTP 请求的数量以及 JavaScript 和 CSS 文件所需的带宽 使用 Bundler 和 Minifier 扩展 16-23

准备示例项目

本章,我使用【空】模板新建了一个名为 WorkingWithVisualStudio 的【ASP.NET Core Web应用程序(.NET Core)】项目。我在 Startup.cs 文件中使用默认配置启用了 MVC。如清单6-1所示。

清单 6-1:WorkingWithVisualStudio 文件夹下的 Startup.cs 文件,启用 MVC

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvcWithDefaultRoute();
        }
    }
}

创建模型

我创建了一个 Models 文件夹,并在其中添加了一个类文件 Product.cs 用于定义如清单6-2所示的类。

清单 6-2:Models 文件夹下的 Product.cs 文件的内容

namespace WorkingWithVisualStudio.Models
{
    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

为创建Product对象的简单仓库,我在 Models 文件夹下添加了一个名为 SimpleRepository.cs 的类文件,并用于定义如清单6-3所示的类。

清单 6-3:Models 文件夹下的 SimpleRepository.cs 文件的内容

using System.Collections.Generic;
namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products
            = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;
        public SimpleRepository()
        {
            var initialItems = new[] {
                new Product { Name = "Kayak", Price = 275M },
                new Product { Name = "Lifejacket", Price = 48.95M },
                new Product { Name = "Soccer ball", Price = 19.50M },
                new Product { Name = "Corner flag", Price = 34.95M }
            };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);
    }
}

本类用于在内在中存储模型类,这意味着应用程序停止或重启后,所有对模型的修改都会丢失。对于本章示例来说,一个非持久仓库已经足够,但这不是一种可以在许多真实项目中使用的方法;有关创建存储库的示例,请参阅第8章,该存储库使用关系数据库持久存储模型对象。

注意:在清单6-3中,我定义了一个名为SharedRepository的静态属性,提供对单个SimpleRepository对象的访问,该对象可在整个应用程序中使用。这不是最佳实践,但我想演示一下您在 MVC 开发中将遇到的一个常见问题;我在第18章中描述了一种更好的使用共享组件的方法。

创建控制器和视图

我在项目中添加了一个 Controllers 文件夹,并添加了一个名为 HomeController.cs 的类文件,用于定义如清单6-4所示的控制器。

清单 6-4:Controllers 文件夹下的 HomeController.cs 文件的内容

using Microsoft.AspNetCore.Mvc;
using WorkingWithVisualStudio.Models;

namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
            => View(SimpleRepository.SharedRepository.Products);
    }
}

只有一个 action —— 名为Index —— 获取所有模型对象并将它们传递给View方法以渲染默认视图。为添加这个视图,我创建了 Views/Home 文件夹并添加了一个名为 Index.cshtml 的视图文件,内容如清单6-5所示。

清单 6-5:Views/Home 文件夹下的 Index.cshtml 文件的内容

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Working with Visual Studio</title>
</head>
<body>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@p.Price</td>
                </tr>
            }
        </tbody>
    </table>
</body>
</html>

视图包含一个表格,使用了foreach循环为每个模型对象创建行,每行都包含NamePrice属性的单元格。运行示例程序,将会看到如图6-1所示的结果。

图6-1 运行示例应用程序

管理软件包

ASP.NET Core MVC 项目需要两个不同类型的软件包。在接下来的部分中,我将描述每种类型的包以及 Visual Studio 为管理它们提供的工具。

理解 NuGet

Visual Studio 为项目中所包含的 .NET 包提供了一个图形管理工具。要打开此工具,在【工具】➤【NuGet 包管理器】菜单中选择【管理解决方案的 NuGet 程序包】。NuGet 工具打开后会显示已经安装的包列表,如图6-2所示。

图6-2 使用 NuGet 包管理器

在【已安装】选项卡中,提供了项目中已经安装的程序包的摘要。【浏览】选项卡可用于查找和安装新的程序包,【更新】选项卡用于列出包的已发布的最新版本。

理解 Microsoft.AspNetCore.All 程序包

如果您用过早期版本的 ASP.NET Core,应该已经熟悉需要向一个新项目中添加一长串 NuGet 包。ASP.NET Core 2 使用了不同的方法,现在只需要依赖一个名为Microsoft.AspNetCore.All的包。

Microsoft.AspNetCore.All包是一个元包,它包含 ASP.NET Core 和 MVC 框架需要的所有单个 NuGet 包。这意味着您不再需要一个一个地添加包。当发布应用程序时,任何属于元包但未被应用程序使用的单个包都将被删除,这家确保了您不会布置不需要的包。

理解 NuGet 包列表和定位

NuGet 工具在<projectname>.csproj文件中跟踪项目的包,其中<projectname>使用项目名称替代。对于示例应用程序来说,NuGet 包的详细信息存储在名为 WorkingWithVisualStudio.csproj 的文件中。Visual Studio 在解决方案资源管理器窗口中不会显示 .csproj 文件。要编辑此文件,在【解决方案资源管理器】中右键单击项目,在弹出菜单中选择【编辑 WorkingWithVisualStudio.csproj】。Visual Studio 将打开文件并编辑。.csproj 是 XML 文件,您将看到这样一个元素,它将 ASP.NET Core 元包添加到项目中,如下所示:

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
</ItemGroup>

一个程序包需要指名称和所需的版本号。尽管元包包含了所有 ASP.NET Core MVC 所需的功能,您仍然需要向项目中添加包,以便可以使用附加的功能。可以使用图6-2所示的界面或使用命令行工具来添加包。你还可以直接编辑 .csproj 文件,Visual Studio 将检查更改并下载安装所添加的程序包。

当您使用 NuGet 向项目添加程序包时,会自动安装程序包以及它所依赖的任何包。你可以在【解决方案资源管理器】中依次展开【依赖项】➤【NuGet】来查看 NuGet 包及其依赖项,它显示了 .csproj 文件中的每个包及其依赖项。ASP.NET Core 元包有大量的依赖项,其中一些可以在图6-3中看到。

图6-3 解决方案资源管理器的引用部分

理解 Bower

客户端程序包包含了发送到客户端的内容,如 JavaScript 文件、CSS 样式或图片。NuGet 也用来管理这些项目,但 ASP.NET Core MVC 现在使用一个名为 Bower 的工具。Bower 是一种开源工具,它是在微软和 .NET 世界之外开发的,广泛应用于非 ASP.NET Core Web 应用程序的开发。

注意:Bower 最近被贬低了。您可能会看到建议使用替代工具的警告;但是 Bower 仍在被积极维护,对 Bower 的支持被集成到 Visual Studio 中。在某种程度上,您可以期望微软支持不同的工具来管理客户端包,但是您应该继续使用 Bower,直到这种情况发生。

理解 Bower 包列表

Bower 包通过 bower.json 文件指定。创建这个文件,请在【解决方案资源管理器】中右键单击 WorkingWithVisualStudio 项目,在弹出菜单中选择【添加】➤【新建项】,在【ASP.NET Core】➤【常规】类别中选择【JSON 文件】,如图 6-4 所示。

图6-4 创建 Bower 配置文件

将名称更改为 bower.json,并单击【添加】按钮将文件添加至项目,输入如清单6-6所示代码:

译者注:按书中原文所述,Visual Studio 有【Bower 配置文件】模板可用于创建 Bower 配置文件。但最新版 Visual Studio 已经取消了此模板,Visual Studio 15.8版本转而使用 LibMan 管理客户端包。所以这里只能通过 json 文件的方式添加。清单6-6为使用模板生成的默认代码,按我的方式添加 json 文件是没有此代码的,读者可跳过6-6直接输入清单6-7的代码。

清单 6-6:bower.json 文件默认内容

{
    "name": "asp.net",
    "private": true,
    "dependencies": {
    }
}

清单6-7显示了将客户端包添加到 bower.json 文件中,这是通过向dependencies部分添加一个条目来完成的。

提示:Bower 包的存储库是http://bower.io/search,您可以在其中搜索要添加到项目中的包。

清单 6-7:在 bower.json 文件中添加包

{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "4.1.3"
  }
}

清单中的新增代码将 Bootstrap CSS包添加到示例项目中。编辑 bower.json 文件时,Visual Studio 将为您提供包名列表,并列出可用包的版本,如图6-5所示。 图6-5 列出客户端包的可用版本

在编写本书时,最新版本的 bootstrap 包是4.1.3。注意,提供了三个选项4.1.3^4.1.3、和~4.1.3。版本号可以在 bower.json 文件中以多种不同的方式指定,其中最有用的方法见表6-2。指定包的最安全的方法是使用显式的版本号。这确保您将始终使用相同的版本,除非您有意更新 bower.json 文件以请求另一个版本。

提示:对于本书中的例子,我直接创建和编辑 bower.json 文件。该文件很容易编辑,并且它有助于确保您在跟随过程中得到预期的结果。Visual Studio 还提供了一个图形化工具来管理 Bower 包,它可以通过右键单击 bower.json 文件并从弹出菜单中选择【管理 Bower 程序包】来打开。

表 6-2:bower.json 文件中版本号的通用格式

格式 描述
4.1.3 直接表示版本号将以完全匹配的版本号的方式安装软件包,如 4.1.3。
* 使用星号将允许 Bower 下载和安装任何版本的软件包。
>4.1.3 >=4.1.3 >>=作为版本号的前缀将允许 Bower 安装大于或等于给定版本号的任何版本的包。
<4.1.3 <=4.1.3 <<=作为版本号的前缀将允许 Bower 安装小于或等于给定版本号的任何版本的包。
~4.1.3 在版本号前加上一个~字符将允许 Bower 的安装补丁级别号(三位版本号中的最后一位)不匹配的版本)。例如,指定~4.1.3将允许 Bower 安装4.1.44.1.5版本(它们是4.1.3版本的补丁),但不允许安装4.2.0版本(这将是一个新的次要版本)。
^4.1.3 在版本号前加上插入^字符将允许 Bower 安装次要发行号(三位版本号中的第二位)或补丁号不匹配的版本。例如,指定^4.1.3将允许 Bower 安装4.3.14.4.04.5.0版本,但不允许安装5.0.0版本。

Visual Studio 监控 bower.json 文件的更改并自动使用 Bower 工具下载并安装程序包。当您保存清单6-7所示的文件,Visual Studio 将下载 Bootstrap 包并安装进 wwwroot/lib 文件夹,如图6-6所示。

译者注:杯具啊!我这里虽然按照要求添加了 bower.json 文件,但 wwwroot/lib 下什么都没有,只在依赖项里出现。看来微软是真不想支持 Bower 了,只能咬着牙用原图翻译完这一节了。

图6-6 在项目中添加客户端包

像 NuGet 一样,Bower 管理您添加到项目中的包的依赖关系。Bootstrap 依赖于 jQuery javascript 库用于它的一些高级特性,这就是为什么图中显示了两个包。您可以通过展开【解决方案资源管理器】中的【依赖项】来查看包及其依赖项的列表,如图6-7所示。

图6-7 检查客户端包及其依赖关系

升级 Bootstrap 程序包

要升级 Bootstrap 程序包,请更改 bower.json 文件内的版本号,如清单6-8所示。

清单 6-8:WorkingWithVisualStudio 文件夹下的 bower.json 文件,更改程序包版本

{
    "name": "asp.net",
    "private": true,
    "dependencies": {
    "bootstrap": "5.0.0"
    }
}

当你保存 bower.json 文件,Visual Studio 将下载新版本的 Bootstrap 程序包。

使用 LibMan

本节为译者自行编写的附加章节。在翻译本书时,我使用的是 Visual Studio 最新版本:15.8。在此版本中,微软已经不再完美支持 Bower。安装的开发包无法自动加载进 wwwroot 文件夹,鉴于此,我决定在后面章节中使用 LibMan 代替 Bower 内容。本章简单介绍 LibMan 使用方法。

LibMan 是微软自己编写的 Visual Studio 内建的客户端包管理器,全称 Microsoft Library Manager。下面是源码地址及文档,详细使用方法参考上面的文档即可,我这里只写最基本使用: https://github.com/aspnet/LibraryManager

在【解决方案资源管理器】中的 WorkingWithVisualStudio 项目上单击鼠标右键,在弹出的菜单中选择【添加】➤【添加客户端库】打开【添加客户端库】窗口。

在【添加客户端库】窗口中,在【库】栏输入“twitter-bootstrap”,输入到一半即可选择,让它自动完成。最终窗口如下图所示:

注意目标位置栏为 “wwwroot/lib/twitter-bootstrap/”。鼠标单击【安装】按钮,将 Bootstrap 加入的项目中。接下来查看【解决方案资源管理器】可以发现生成了一个新的文件 libman.json。wwwroot 文件夹下已经出现 Bootstrap 的安装文件。

打开 libman.json 文件,查看源码,其实和 Bower 差不多:

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "library": "twitter-bootstrap@4.1.3",
      "destination": "wwwroot/lib/twitter-bootstrap/"
    }
  ]
}

好,就到这里,下面继续翻译正文。

理解迭代开发

Web应用程序开发通常是一个迭代过程,您可以对视图或类进行小的更改,并运行应用程序来测试它们的效果。MVC 和 Visual Studio 一起工作以支持这种迭代方法,以便快速、容易地看到更改的效果。

更改 Razor 视图

在开发过程中,一旦收到来自浏览器的 HTTP 请求,对 Razor 视图的更改就会生效。要演示这是如何工作的,请从【调试】菜单中选择【开始调试】启动应用程序,浏览器打开并显示数据后,将 Index.cshtml 文件更改为如清单6-9所示代码。

清单 6-9:更改 Index.cshtml 文件

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{ p.Price:C2}")</td>
                </tr>
            }
        </tbody>
    </table>
</body>
</html>

保存对 Index 视图的更改并使用,使用浏览器的重载按钮重载 web 页。对视图的更改(添加header元素和将Price模型属性格式化为货币)生效,并在浏览器中显示,如图6-8所示。

提示:我在第21章中解释了 Razor 视图准备使用的过程。

图6-8 改变视图

更改 C# 类

对于 C# 类,包括控制器和模型,处理更改的方式取决于启动应用程序的方式。在之后章节,我描述了可用的两种方法,它们是通过调试菜单中的不同项来选择的,如表6-3所述,以供快速参考。

表 6-3:调试菜单项

菜单项 描述
开始执行(不调试) 当收到 HTTP 请求时,项目中的类会自动编译,从而允许更动态的开发体验。应用程序是在没有调试器的情况下运行的,从而无法控制代码的执行。
开始调试 您必须显式编译项目并重新启动应用程序以使更改生效。调试器在运行时附加到应用程序,允许检查其状态并分析任何问题。

类的自动编译

在正常开发过程中,快速迭代周期可以让您立即看到更改的效果,无论是添加新 action 的效果还是更改视图模型数据的选择方式。对于这种开发,Visual Studio 支持在从浏览器接收到 HTTP 请求并时立即检测更改,并自动重新编译类。要了解这是如何工作的,请从 Visual Studio 【调试】菜单中选择【开始执行(不调试)】。一旦浏览器显示应用程序数据,就对 Home 控制器进行清单6-10所示的更改。

清单 6-10:HomeController.cs 文件,筛选模型数据

using Microsoft.AspNetCore.Mvc;
using WorkingWithVisualStudio.Models;
using System.Linq;

namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
            => View(SimpleRepository.SharedRepository.Products
                .Where(p => p.Price < 50));
    }
}

所做更改为使用 LINQ 筛选Product对象,以便只将Price属性小于 50 的对象传递给视图。保存对控制器类文件的更改,然后重载浏览器窗口而不在 Visual Studio 中停止或重新启动应用程序。来自浏览器的 HTTP 请求将触发编译过程,应用程序将使用修改后的控制器类重新启动,产生如图6-9所示的结果,导致从表中省略kayak产品。 图6-9 自动编译类

当一切都要计划时,自动编译功能是有用的。缺点是编译器和运行时错误显示在浏览器中,而不是 Visual Studio 中,这会使我们很难弄清楚当出现问题时发生了什么。例如,清单6-11显示了对存储库中的模型对象集合添加一个null引用。

清单 6-11:SimplerRepository.cs 文件,添加一个空引用

using System.Collections.Generic;

namespace WorkingWithVisualStudio.Models
{
    public class SimpleRepository
    {
        private static SimpleRepository sharedRepository = new SimpleRepository();
        private Dictionary<string, Product> products
            = new Dictionary<string, Product>();
        public static SimpleRepository SharedRepository => sharedRepository;
        public SimpleRepository()
        {
            var initialItems = new[] {
                new Product { Name = "Kayak", Price = 275M },
                new Product { Name = "Lifejacket", Price = 48.95M },
                new Product { Name = "Soccer ball", Price = 19.50M },
                new Product { Name = "Corner flag", Price = 34.95M }
            };
            foreach (var p in initialItems)
            {
                AddProduct(p);
            }
            products.Add("Error", null);
        }
        public IEnumerable<Product> Products => products.Values;
        public void AddProduct(Product p) => products.Add(p.Name, p);
    }
}

直到应用程序运行时,才会出现像空引用这样的问题。重新加载浏览器页面将导致编译SimpleRepository类,并重新启动应用程序。当 MVC 创建控制器类的实例以处理来自浏览器的 HTTP 请求时,HomeController构造函数将实例化SimpleRepository类,后者将尝试处理清单中添加的空引用。null值会导致问题,但不清楚问题是什么,因为浏览器没有显示有用的消息。

启用开发者异常页

在开发过程中出现问题时,在浏览器窗口中显示更多有用的信息是有用的。这可以通过启用开发者异常页来实现,这需要在 Startup 类中进行配置更改,如清单6-12所示。

我在第14章中详细解释了 Startup 类的角色,但现在只需知道调用UseDeveloperExceptionPage扩展方法就可以设置描述性错误页。

清单 6-12:Startup.cs 文件,启用开发者异常页

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseMvcWithDefaultRoute();
        }
    }
}

如果您重载浏览器窗口,自动编译过程将重建应用程序,并在浏览器中生成更有用的错误消息,如图6-10所示。 图6-10 开发者异常页

浏览器显示的错误消息足以解决简单的问题,特别是因为迭代开发风格意味着最近所做的更改可能是造成这种情况的原因。但是对于更复杂的问题和不立即显现的问题,Visual Studio 调试器是必需的。

使用调试器

Visual Studio 还支持使用调试器运行 MVC 应用程序,调试器允许暂停执行以检查应用程序的状态和请求在代码中通过的路径。这需要不同的开发风格,因为直到应用程序重启才会应用对 C# 类的修改(尽管对 Razor 视图的更改仍会自动生效)。

这种开发风格不像使用自动编译功能那样动态,但是 Visual Studio 调试器非常优秀,可以更深入地了解应用程序的工作方式。

要使用调试器运行应用程序,请从 Visual Studio 【调试】菜单中选择【开始调试】。Visual Studio 会在应用程序运行前编译项目中的 C# 类,但您还是可以通过使用【生成】菜单中的项来手动编译代码。

示例应用程序仍然包含空引用,这意味着由SimpleRepository类引发的未处理的NullReferenceException将中断应用程序并将执行控制传递给开发人员,如图6-11所示。

提示:如果调试器未拦截异常,请在 Visual Studio 【调试】菜单中选择【窗口】➤【异常设置】,确保勾选公共语言运行库异常列表中的所有异常类型。

译者注:默认情况下是无法拦截异常的,其实无需选择所有异常类型,只需选中一项即可,就是下图中的【Common Language Runtime Exceptions】项:

图6-11 未处理异常

设置断点

调试器没有指出问题的根本原因,只是显示了问题出现的地点。Visual Studio 突出显示的语句表明,使用 LINQ 筛选对象时出现了问题,但是需要做一些工作才能深入了解细节并找到根本原因。

断点是一种指令,它告诉调试器停止应用程序的执行并将控制权交给程序员。您可以检查应用程序的状态,查看正在发生的情况,并且可以选择再次恢复执行。

要创建断点,右键单击一条语句并从弹出菜单中选择【断点】➤【插入断点】。作为演示,将一个断点应用到SimpleRepository类中的AddProduct方法中,如图6-12所示。

译者注:其实不用这么麻烦,在红点处点一下设置断点,再点一下取消断点

图6-12 创建断点

选择【调试】➤【开始调试】来使用调试器启动应用程序,如果程序已经运行,选择【调试】➤【重新启动】。在浏览器的初始 HTTP 请求期间,SimpleRepository类将被实例化,代码的执行将到达断点,此时应用程序的执行将停止。

此时,您可以使用 Visual Studio 【调试】菜单项或窗口顶部的控件来控制应用程序的执行,或者使用【调试】➤【窗口】菜单中可用的不同调试器窗口来检查应用程序状态。

在代码编辑器中查看数据值

断点最常见的用途是查找代码中的 bug。在修复bug之前,您必须弄清楚正在发生什么,而 Visual Studio 提供的最有用的功能之一是在代码编辑器中查看和监视变量的值。

如果将鼠标移到由调试器突出显示的AddProduct方法中的p参数上,就会出现一个弹出窗口,显示p的当前值,如图6-13所示。很难分辨出弹出,所以我在图中显示了一个放大的版本。

图6-13 检查数据值

由于数据对象是在与断点相同的构造函数中定义的,因此这一点可能并不令人印象深刻,但此功能适用于任何变量。您可以探索值以查看其属性和字段值。每个值的右侧都有一个小引脚按钮,可以用于在代码执行继续时监视此值。

将鼠标悬停在p变量上,并固定Product引用。展开固定的引用,这样您还可以固定NamePrice属性,从而产生如图6-14所示的效果。

图6-14 在代码编辑器中固定值

从 Visual Studio 【调试】菜单中选择【继续】以继续执行应用程序。由于应用程序正在执行foreach循环,当再次遇到断点时,执行将再次停止。固定值显示分配给p变量的对象及其属性如何变化,如图6-15所示。

图6-15 使用固定值监视状态的改变

使用局部变量窗口

一个相关的功能是局部变量窗口,它是通过选择【调试】➤【窗口】➤【局部变量】菜单项来打开的。局部变量窗口以类似于固定值的方式显示数据值,但是它显示了与断点相关的所有本地对象,如图6-16所示。

图6-16 局部变量窗口

每次选择【继续】时,应用程序的执行将恢复,foreach循环将处理另一个对象。如果一直【继续】,将在局部变量窗口和代码编辑器固定值中都会出现空引用。通过使用调试器控制应用程序的执行,您可以追踪代码流向并了解正在发生的事情。

我可以通过清理Product对象集合来解决空引用问题,但另一种方法是使控制器更健壮,如清单6-13所示,我已经使用了空条件运算符来检查空值(如第4章所述)。

清单 6-13:HomeController.cs 文件,修复空引用问题

using Microsoft.AspNetCore.Mvc;
using WorkingWithVisualStudio.Models;
using System.Linq;

namespace WorkingWithVisualStudio.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
            => View(SimpleRepository.SharedRepository.Products
                .Where(p => p?.Price < 50));
    }
}

通过右键单击应用了断点的语句,并从弹出菜单中选择【删除断点】以禁用断点。重新启动应用程序,您将看到图6至17所示的简单数据表。

图6-17 修复 bug

与真正需要查找 bug 的问题相比,这是一个非常简单的问题,但是 Visual Studio 调试器非常优秀,通过使用应用程序的许多不同视图,并控制执行,您可以真正深入了解细节,以找出出错的原因。

使用浏览器链接

浏览器链接功能可以通过将一个或多个浏览器置于 Visual Studio 的控制下来简化开发过程。如果您需要查看更改对不同浏览器的影响,则此功能非常有用。浏览器链接功能可以使用调试器,也可以不使用调试器,但在使用自动类编译功能时,我发现它最有用,因为它意味着我可以修改项目中的任何文件并查看更改的效果,而无需切换到浏览器并手动重新加载页面。

设置浏览器链接

启用浏览器链接需要对 Startup 类进行配置更改,如清单6-14所示。

清单 6-14:WorkingWithVisualStudio 文件夹下的 Startup.cs 文件,启用浏览器链接

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseBrowserLink();
            app.UseMvcWithDefaultRoute();
        }
    }
}

使用浏览器链接

要理解浏览器链接是如何工作的,在 Visual Studio 【调试】菜单中选择【开始执行(不调试)】,Visual Studio 将启动应用程序,并打开一个新的浏览器窗口显示结果。检查发送到浏览器的 HTML,你会看到它包含这样的附加部分:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            <tr><td>Lifejacket</td><td>&#xA3;48.95</td></tr>
            <tr><td>Soccer ball</td><td>&#xA3;19.50</td></tr>
            <tr><td>Corner flag</td><td>&#xA3;34.95</td></tr>
        </tbody>
    </table>
<!-- Visual Studio Browser Link -->
<script type="application/json" id="__browserLink_initializationData">
    {"requestId":"968949d8affc47c4a9c6326de21dfa03","requestMappingFromServer":false}
</script>
<script type="text/javascript"
    src="http://localhost:55356/d1a038413c804e178ef009a3be07b262/browserLink"
    async="async">
</script>
<!-- End Browser Link -->
</body>
</html>

提示:如果没有看到附加部分,选择图6-18所示的【启用浏览器链接】并重载浏览器。

Visual Studio 向发送到浏览器的 HTML 添加一对脚本元素,用于打开返回到应用服务器的长期 HTTP 连接,以便 Visual Studio 可以强制浏览器重新加载页面。清单6-15显示了对 Index 视图的更改,它将说明使用浏览器链接的效果。

清单 6-15:Index.cshtml 文件,添加一个时间戳

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Working with Visual Studio</title>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{ p.Price:C2}")</td>
                </tr>
            }
        </tbody>
    </table>
</body>
</html>

保存对视图文件所做修改,并在 Visual Studio 工具栏的【浏览器链接】菜单中选择【刷新链接的浏览器】,如图6-18所示。(如果浏览器链接不工作,请重载浏览器或重启 Visual Studio 后再次尝试)

图6-18 使用浏览器链接重载浏览器

发送到浏览器的 HTML 中嵌入的 JavaScript 代码将重新加载页面,显示添加的效果,即添加一个简单的时间戳。每次选择 Visual Studio 菜单项时,浏览器都会向服务器发出新的请求。请求将导致 Index 视图重新渲染以生成具有更新时间戳的新 HTML 页面。

注意:浏览器链接的脚本元素仅嵌入到成功的响应中,这意味着如果在编译类、呈现 Razor 视图或处理请求时抛出异常,则浏览器和 Visual Studio 之间的连接将丢失,一旦解决了问题,就必须使用浏览器重新加载页面。

使用多浏览器

浏览器链接可用于同时在多个浏览器中显示应用程序,当您希望消除浏览器之间的实现差异,或查看应用程序如何在桌面浏览器和移动浏览器上混合渲染时,此链接非常有用。

要选择浏览器,请从 Visual Studio 工具栏上的【IIS Express】按钮中选择【使用以下工具浏览】,如图6-19所示。

图6-19 选择多浏览器

Visual Studio 显示了它所知道的浏览器列表。图6-20显示了我在我的系统上安装的浏览器,其中一些浏览器随 Windows 一起安装的(Internet Explorer 和 Edge),还有一些是我安装的,因为它们被广泛使用。

图6-20 在列表中选择浏览器

Visual Studio 在安装过程中查找常见的浏览器,但可以使用【添加】按钮设置未自动发现的浏览器。您还可以设置第三方测试工具,比如在云托管虚拟机上运行浏览器的浏览器堆栈,这样您就不必管理大量的操作系统和浏览器来进行测试。

图中我选择了三个浏览器:Chrome、Internet Explorer 和 Edge。单击【浏览】按钮将启动所有三个浏览器,并让它们加载示例应用程序的 URL,如图6-21所示。

图6-21 使用多浏览器

您可以通过选择【浏览器链接仪表板】➤【浏览器链接仪表板】菜单项来查看浏览器链接管理,该菜单项打开如图6-22所示的窗口。仪表板显示每个浏览器显示的 URL,每个浏览器都可以单独刷新。

图6-22 浏览器链接仪表板窗口

准备用于部署的 JavaScript 和 CSS

当您创建 Web 应用程序的客户端部分时,通常会创建许多定制的 JavaScript 和 CSS 文件,这些文件用于补充 Bower 所安装的包中的文件。这些文件需要处理优化,以便在生产环境中交付,以便将 HTTP 请求的数量和向客户端传递它们所需的网络带宽最小化。这个过程被称为打包压缩。在本节中,我将解释如何启用静态内容的交付,以及如何为部署做好准备。

启用静态内容传递

ASP.NET Core 支持将静态文件从 wwwroot 文件夹传递给客户端,但使用【空】模板创建项目的情况下不启用。通过将清单6-16所示的语句添加到Startup类中,启用对静态内容的支持。

清单 6-16:WorkingWithVisualStudio 文件夹下的 Startup.cs 文件,启用静态内容

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace WorkingWithVisualStudio
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseBrowserLink();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }
    }
}

将静态内容添加至项目

为了演示打包和压缩过程,我需要向项目添加一些静态内容,并将其合并到示例应用程序中。首先,我创建了 wwwroot/CSS 文件夹,这是自定义 CSS 文件的常规位置。然后,我使用【样式表】模板添加了一个名为 first.css 的文件,如图6-23所示。【样式表】模板可在【ASP.NET Core】➤【web】➤【内容】部分找到。

图6-23 创建 CSS 样式表

编辑 first.css 文件以添加清单6-17所示的 CSS 样式。

清单 6-17:wwwroot/css 文件夹下的 first.css 文件的内容

h3 {
    font-size: 18pt;
    font-family: sans-serif;
}

table, td {
    border: 2px solid black;
    border-collapse: collapse;
    padding: 5px;
    font-family: sans-serif;
}

我重复了这个过程,在 wwwroot/css 文件夹中创建了另一个名为 second.css 的样式表,内容如清单6-18所示。

清单 6-18:wwwroot/css 文件夹下的 second.css 文件的内容

p {
    font-family: sans-serif;
    font-size: 10pt;
    color: darkgreen;
    background-color: antiquewhite;
    border: 1px solid black;
    padding: 2px;
}

自定义 JavaScript 文件通常存储于 wwwroot/js 文件夹下。我创建了这个文件夹并使用【JavaScript 文件】模板来创建一个名为 third.js 的文件。如图6-24所示。【JavaScript 文件】模板在【ASP.NET Core】➤【Web】➤【脚本】部分。

图6-24 创建 JavaScript 文件

我在新文件中添加了一些简单的 JavaScript 代码,如清单 6-19 所示。

清单 6-19:wwwroot/js 文件夹下的 third.js 文件的内容

document.addEventListener("DOMContentLoaded", function () {
    var element = document.createElement("p");
    element.textContent = "This is the element from the third.js file";
    document.querySelector("body").appendChild(element);
});

我需要一个更为简单 JavaScript 文件,并在 wwwroot/js 文件夹下创建一个名为 fourth.js 的文件,添加如清单6-20所示的代码。

清单 6-20:wwwroot/js 文件夹下的 fourch.js 文件的内容

document.addEventListener("DOMContentLoaded", function () {
    var element = document.createElement("p");
    element.textContent = "This is the element from the fourth.js file";
    document.querySelector("body").appendChild(element);
});

更新视图

最后的准备步骤是更新 Index.cshtml 视图以使用新的 CSS 样式表和 JavaScript 文件,如清单6-21所示。

清单 6-21:Views/Home 文件夹下的 Index.cshtml 文件,添加 script 和 link 元素

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/first.css" />
    <link rel="stylesheet" href="css/second.css" />
    <script src="js/third.js"></script>
    <script src="js/fourth.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{ p.Price:C2}")</td>
                </tr>
            }
        </tbody>
    </table>
</body>
</html>

运行程序,您将看到如图 6-25 所示的内容。现有内容已被 CSS 样式表样式化,而 JavaScript 代码添加了新内容。

图6-25 运行示例应用程序

MVC应用程序中的打包和压缩

此时一共有4个静态文件,浏览器为了获取静态文件需要发送4个请求。而且这些文件中的每个都比应传递给客户端所需的更多的带宽,因为它们包含对开发人员有意义但对浏览器毫无意义的空白和变量名称。

合并相同类型的文件称为打包。使文件更小称为压缩。这两个任务都是在 ASP.NET Core MVC应用程序中由 Visual Studio的 Bundler & Minifier 扩展执行的。

安装 Visual Studio 扩展

第一步是安装扩展,选择【工具】➤【扩展和更新】菜单,并单击【联机】类别以显示可用 Visual Studio 扩展库。在窗口右上角搜索栏输入“Bundler”,如图 6-26 所示。定位到【Bundler & Minifier】扩展,并单击【下载】按钮将其添加进 Visual Studio。重启 Visual Studio 完成安装进程。

图6-26 查找 Visual Studio 扩展

打包和压缩文件

一旦安装了扩展,重新启动 Visual Studio 并打开示例项目。通过添加扩展,您可以在【解决方案资源管理器】中选择相同类型的多个文件,将它合并在一起,并压缩它们的内容。例如,在【解决方案资源管理器】中选择 first.css 和 second.css 文件,右键单击,然后在弹出菜单中选择【Bundler & Minifier】➤【Bundle and Minify Files】,如图6-27所示。

图6-27 打包和压缩 CSS 文件

将输出文件保存为 bundle.css,扩展名将为 CSS 文件。【解决方案资源管理器】显示一个新的 bundle.css 项,你可以展开它以显示压缩的文件,名为 bundle.min.css。如果你打开压缩文件,将看到两个独立 CSS 文件的内容已经合并,所有空白都已被删除。您可能不想直接处理这个文件,但是它更小,只需要一个 HTTP 连接就可以将 CSS 样式传递给客户端。

对 third.js 和 fourth.js 文件重复这个过程,在 wwwroot/js 文件夹下创建新文件 bundle.js 和 bundle.min.js。

警告:确保按浏览器加载文件的顺序选择文件,以保留输出文件中的样式或代码语句的顺序。例如,在选择 fourth.js 文件之前,确保选择了 third.js 文件,以确保以正确的顺序执行代码。

在清单6-22中,我已经将单个文件的link元素替换为请求 Index.cshtml 视图中打包和压缩的文件的link元素。

清单 6-22:Index.cshtml 文件,使用打包和压缩文件

@model IEnumerable<WorkingWithVisualStudio.Models.Product>
@{ Layout = null; }

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Working with Visual Studio</title>
    <link rel="stylesheet" href="css/bundle.min.css" />
    <script src="js/bundle.min.js"></script>
</head>
<body>
    <h3>Products</h3>
    <p>Request Time: @DateTime.Now.ToString("HH:mm:ss")</p>
    <table>
        <thead>
            <tr><td>Name</td><td>Price</td></tr>
        </thead>
        <tbody>
            @foreach (var p in Model)
            {
                <tr>
                    <td>@p.Name</td>
                    <td>@($"{ p.Price:C2}")</td>
                </tr>
            }
        </tbody>
    </table>
</body>
</html>

如果运行应用程序,则没有任何视觉上的更改,但是打包和压缩的文件用于向浏览器提供在单独文件中定义的所有样式和代码。

在执行打包和压缩操作时,扩展在项目根文件夹下一个名为 bundleconfig.json 的文件保存了文件处理过程记录。下面是为示例应用程序中的文件生成的配置:

[
  {
    "outputFileName": "wwwroot/css/bundle.css",
    "inputFiles": [
      "wwwroot/css/first.css",
      "wwwroot/css/second.css"
    ]
  },
  {
    "outputFileName": "wwwroot/js/bundle.js",
    "inputFiles": [
      "wwwroot/js/fourth.js",
      "wwwroot/js/third.js"
    ]
  }
]

扩展自动监视输入文件的更改,并在有更改时重新生成输出文件,以确保您所做的任何编辑都反映在打包和压缩的文件中。为了演示,清单6-23显示了对 third.js 文件的更改。

清单 6-23:更改 third.js 文件

document.addEventListener("DOMContentLoaded", function () {
    var element = document.createElement("p");
    element.textContent = "This is the element from the (modified) third.js file";
    document.querySelector("body").appendChild(element);
});

一旦保存了文件,扩展名就会重新生成 bundle.min.js 文件。如果重新加载浏览器,您将看到图6-28所示的更改

图6-28 打包和压缩文件的更改检测

总结

在本章中,我描述了 Visual Studio 为 Web 应用程序开发提供的特性,包括自动类编译、浏览器链接以及打包和压缩。在下一章中,我将介绍 ASP.NET Core MVC 项目是如何用于单元测试的。

;

© 2018 - IOT小分队文章发布系统 v0.3